package cn.emailChat.ai.service;

import cn.emailChat.ai.domain.ContactProfile;
import cn.emailChat.ai.domain.ConversationSummary;
import cn.emailChat.ai.domain.Email;
import cn.emailChat.ai.dto.MailInboundEvent;
import cn.emailChat.ai.dto.MemoryContext;
import cn.emailChat.ai.repo.ContactProfileRepo;
import cn.emailChat.ai.repo.ConversationSummaryRepo;
import cn.emailChat.ai.repo.EmailRepo;
import cn.emailChat.ai.service.util.SubjectNormalizer;
import cn.emailChat.ai.service.util.Summarizer;
import cn.emailChat.ai.service.util.ThreadHeuristics;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;



/**
 * @Description 记忆服务的实现
 * @Author susu
 * @Date 2025/8/20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MemoryServiceImpl implements MemoryService{
    private final EmailRepo emailRepo;
    private final ConversationSummaryRepo csRepo;
    private final ContactProfileRepo cpRepo;
    private final Summarizer summarizer;          // 摘要
    private final ThreadHeuristics heuristics;    // 线程打分&闭合判断

    // 系统发信邮箱（用它区分“我们发的回复” 与 “对方的来信”）
    @Value("${spring.mail.username}")
    private String selfAddress;

    // 兜底自动回复文案
    @Value("${mail.reply.fixed-content:您好，已收到您的邮件，我们会尽快处理。（此为自动回复）}")
    private String fallbackFixed;

    // 线程候选时间窗（天）
    @Value("${memory.thread.window.days:30}")
    private int threadWindowDays;

    // 近 N 条片段
    @Value("${memory.recent.turns:5}")
    private int recentTurnsN;

    @Override
    public String locateThreadId(MailInboundEvent e) {
        final String contact = safeLower(e.getFromEmail());
        final String sNorm = SubjectNormalizer.normalize(e.getSubject());
        final LocalDateTime since = LocalDateTime.now().minusDays(threadWindowDays);

        // 1) 若该联系人已有 OPEN 的 conversation_summary，先从中挑最像的
        List<ConversationSummary> candidates =
                csRepo.findByContactEmailAndStatusOrderByUpdatedAtDesc(contact, "open");
        String bestFromCS = pickBestThreadBySubject(candidates, sNorm, since);
        if (bestFromCS != null) return bestFromCS;

        // 2) 没有现成线程，就根据 Email 单表做一次“软聚类”的回查（同联系人、时间窗内、主题相似）
        List<Email> recentMails = emailRepo.findByFromEmailAndSendTimeAfterOrderBySendTimeDesc(contact, since);
        boolean similarTopicExists = recentMails.stream()
                .map(m -> SubjectNormalizer.normalize(m.getSubject()))
                .anyMatch(norm -> SubjectNormalizer.similarEnough(norm, sNorm));

        if (similarTopicExists) {
            // 仍然新建线程（因为没有已有 CS 记录）——线程ID用稳定 hash
            return heuristics.newThreadId(contact, sNorm, e.getMessageId());
        }

        // 3) 默认新建线程
        return heuristics.newThreadId(contact, sNorm, e.getMessageId());
    }

    @Override
    public MemoryContext buildContext(String threadId, MailInboundEvent e) {
        final String contact = safeLower(e.getFromEmail());
        final String sNorm = SubjectNormalizer.normalize(e.getSubject());

        // 1) 线程摘要
        String convoSum = csRepo.findById(threadId)
                .map(ConversationSummary::getSummary)
                .orElse("");

        // 2) 联系人画像
        String contactSum = cpRepo.findById(contact)
                .map(cp -> cp.getLongTermSummary() == null ? "" : cp.getLongTermSummary())
                .orElse("");

        // 3) 最近片段：直接从 Email 表拿（同联系人、时间窗内），按 subjectNorm 过滤并生成“角色+文本”的短片段
        List<String> recentTurns = collectRecentTurns(contact, sNorm, recentTurnsN);

        return MemoryContext.builder()
                .threadId(threadId)
                .contactEmail(contact)
                .subjectNorm(sNorm)
                .conversationSummary(convoSum == null ? "" : convoSum)
                .contactProfileSummary(contactSum)
                .recentTurns(recentTurns)
                .fallbackFixedContent(fallbackFixed)
                .build();
    }

    @Override
    @Transactional
    public void updateAfterReply(String threadId,
                                 String contactEmail,
                                 String subject,
                                 String lastMessageId,
                                 String lastUserText,
                                 String lastAssistantText) {
        final String contact = safeLower(contactEmail);
        final String sNorm = SubjectNormalizer.normalize(subject);

        // 1) upsert 线程摘要
        ConversationSummary cs = csRepo.findById(threadId).orElseGet(() -> {
            ConversationSummary x = new ConversationSummary();
            x.setThreadId(threadId);
            x.setContactEmail(contact);
            x.setSubjectNorm(sNorm);
            x.setStatus("open");
            return x;
        });

        String old = cs.getSummary() == null ? "" : cs.getSummary();
        String updated = summarizer.updateThreadSummary(old, lastUserText, lastAssistantText);
        cs.setSummary(updated);
        cs.setSubjectNorm(sNorm);
        cs.setLastMessageId(lastMessageId);
        cs.setStatus("open");
        csRepo.save(cs);

        // 2) （可选）闭合判断 → 蒸馏到联系人画像
        if (heuristics.shouldClose(updated, lastUserText, lastAssistantText)) {
            cs.setStatus("closed");
            csRepo.save(cs);

            String distilled = summarizer.distillToLongTerm(updated);
            ContactProfile cp = cpRepo.findById(contact).orElseGet(() -> {
                ContactProfile y = new ContactProfile();
                y.setContactEmail(contact);
                y.setLongTermSummary("");
                return y;
            });
            String merged = mergeLongTerm(cp.getLongTermSummary(), distilled);
            cp.setLongTermSummary(merged);
            cpRepo.save(cp);
        }
    }

    // ===================== 辅助方法 =====================

    private String pickBestThreadBySubject(List<ConversationSummary> candidates,
                                           String sNorm,
                                           LocalDateTime since) {
        // 只挑最近时间窗的 OPEN 线程，按 subjectNorm 完全等/相似度优先
        return candidates.stream()
                .filter(c -> c.getUpdatedAt() == null || !c.getUpdatedAt().isBefore(since))
                .sorted((a, b) -> {
                    // 更像 + 更新
                    int simCmp = Boolean.compare(
                            SubjectNormalizer.similarEnough(b.getSubjectNorm(), sNorm),
                            SubjectNormalizer.similarEnough(a.getSubjectNorm(), sNorm)
                    );
                    if (simCmp != 0) return simCmp;
                    LocalDateTime ua = a.getUpdatedAt(), ub = b.getUpdatedAt();
                    if (ua == null && ub == null) return 0;
                    if (ua == null) return 1;
                    if (ub == null) return -1;
                    return ub.compareTo(ua);
                })
                .map(ConversationSummary::getThreadId)
                .findFirst()
                .orElse(null);
    }

    private List<String> collectRecentTurns(String contactEmail, String sNorm, int limit) {
        LocalDateTime since = LocalDateTime.now().minusDays(threadWindowDays);
        // 取同联系人近 30 天的邮件（来信 + 我们的回信），按时间倒序
        List<Email> mails = emailRepo.findByFromEmailAndSendTimeAfterOrderBySendTimeDesc(contactEmail, since);

        // 过滤到“同一话题”（主题归一化近似）
        List<Email> sameTopic = mails.stream()
                .filter(m -> SubjectNormalizer.similarEnough(
                        SubjectNormalizer.normalize(m.getSubject()), sNorm))
                .sorted(Comparator.comparing(Email::getSendTime).reversed())
                .collect(Collectors.toList());

        // 构造“角色: 文本”的片段，注意长度控制
        List<String> turns = new ArrayList<>();
        for (Email m : sameTopic) {
            String role = isOurReply(m) ? "我们" : "对方";
            String text = safe(m.getBody());
            if (text.length() > 300) text = text.substring(0, 300) + "…";
            turns.add(role + ": " + text);
            if (turns.size() >= limit) break;
        }
        return turns;
    }

    // 通过发件邮箱判断是不是“我们发的邮件”
    private boolean isOurReply(Email m) {
        String from = safeLower(m.getFromEmail());
        return from.equals(safeLower(selfAddress)) || (m.getMessageId() != null && m.getMessageId().endsWith("_reply"));
    }

    private String mergeLongTerm(String oldSum, String distilled) {
        if (oldSum == null || oldSum.isBlank()) return distilled == null ? "" : distilled;
        if (distilled == null || distilled.isBlank()) return oldSum;
        // 朴素合并：去重后拼接（你可换成更聪明的合并器）
        if (oldSum.contains(distilled)) return oldSum;
        return (oldSum + "；" + distilled).replaceAll("[；;]{2,}", "；");
    }

    private String safe(String s) { return s == null ? "" : s.trim(); }
    private String safeLower(String s) { return safe(s).toLowerCase(); }
}
